package steampunk.utils;

import java.awt.geom.Line2D;

public class Mutil {

	/**
	 * Returns the minimum initial velocity required to make an object travel
	 * <i>d</i> given the acceleration <i>a</i> is working on it. If the acceleration
	 * is pushing the object in the same direction you want it to go, this function
	 * just returns 0. Make sure you get your signs right!
	 * @param d - the distance
	 * @param a - the acceleration
	 * @return
	 */
	public static double getVoFor(double d, double a) {
		double unsq = -2 * a * d;
		int negFactor = d>=0?1:-1;
		if (unsq > 0)
			return negFactor * Math.sqrt(unsq);
		return 0; // We'll naturally accelerate in the direction we want to go,
				  // (because a/d is positive), so no NBD.
	}
	
	/**
	 * Returns true if (x, y) is within <i>dist</i> of (h, k).
	 * @param h - A.x
	 * @param k - A.y
	 * @param x - B.x
	 * @param y - B.y
	 * @param dist
	 * @return
	 */
	public static boolean within(double h, double k, double x, double y, double dist) {
		return dist*dist >= (x-h)*(x-h) + (y-k)*(y-k);
	}
	
	/**
	 * Returns true if the line (x1, y1, x2, y2) intersects the circle (h, k, r) 
	 * <br>Where (h, k) is the center point and r is the radius.
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @param h
	 * @param k
	 * @param r
	 * @return
	 */
	public static boolean intLineCircle(
			double x1, double y1, double x2, double y2,
			double h, double k, double r) {
		
		double left = Math.min(x1, x2);
		double right = Math.max(x1, x2);
		double top = Math.min(y1, y2);
		double bottom = Math.max(y1, y2);
		
		// does the circle contain the end points?
		if (within(h, k, x1, y1, r))
			return true;
		if (within(h, k, x2, y2, r))
			return true;
		
		double rise = y2 - y1;
		double run = x2 - x1;
		
		if (rise == 0 && run == 0) {
			// that isn't a line, that's a dot!
			// If the dot was inside the circle, 
			// it would already have been caught in the if
			// statements above this.
			return false;
		}
		
		if (run == 0 || rise == 0) {
			// surprisingly enough, we can easily
			// use the exact same code if the line
			// is vertical or horizontal. (The reason
			// this case is handled at all is because
			// of DivideByZero issues.)
			
			if (h+r<left || h-r>right)
				return false;
			if (k+r<top || k-r>bottom)
				return false;

			if (rise == 0) { // horizontal
				if (h >= left && h <= right)
					return true;
				// end point case is already covered at the top
				return false;
			} else { // vertical
				if (k >= top && k <= bottom)
					return true;
				// end point case is already covered at the top
				return false;
			}
		}
		
		double m  = rise / run;
		
		double mP = run / rise; // perpendicular slope
		
		// equation is straight from whiteboard
		// calculates the x-coordinate of the intersection
		// between the line and the shortest distance between the circle
		// and the line
		double intx = (y1 - k + mP * h + m*x1) / (mP - m);
		double inty = m * (intx - x1) + y1; // y-y1 = m(x-x1)
		
		if (intx < left || intx > right)
			return false;
		if (inty < top || inty > bottom)
			return false;
		
		return within(h, k, intx, inty, r);
	}
	
	
	private static final Line2D.Double liA = new Line2D.Double(0, 0, 1, 1);
	private static final Line2D.Double liB = new Line2D.Double(0, 0, 1, 1);
	public static boolean linesIntersect(
			double Ax1, double Ay1, double Ax2, double Ay2,
			double Bx1, double By1, double Bx2, double By2) {
		synchronized (liA) {
			liA.setLine(Ax1, Ay1, Ax2, Ay2);
			liB.setLine(Bx1, By1, Bx2, By2);
			return liA.intersectsLine(liB);
		}
	}
	
	public static boolean pointOnLine(
			double x1, double y1,
			double x2, double y2,
			double px, double py,
			double thresh) {
		double rise = (y2 - y1);
		double run = (x2 - x1);
		if (rise == 0 && run == 0) {
			// lol what
			return within(x1, y1, px, py, thresh);
		}
		
		if (run == 0 || rise/run > 1) {
			// more accurate to do this by x = m/(y-k) + h
			double m = run / rise;
			double xpos = m / (py-y1) + x1;
			// This isn't quite right, because this isn't
			// necessarily the shortest distance between the point
			// and the line.
			return Math.abs(xpos-px) <= thresh;
		} else {
			// more accurate to do this by y = m(x-h) + k
			double m = rise / run;
			double ypos = m * (py-x1) + y1;
			// This isn't quite right, because this isn't
			// necessarily the shortest distance between the point
			// and the line.
			return Math.abs(ypos-py) <= thresh;
		}
	}
	
	/**
	 * Returns true if the rectangle (rx, ry, rw, rh) intersects the
	 * circle (cx, cy, cr)
	 * @param rx
	 * @param ry
	 * @param rw
	 * @param rh
	 * @param cx
	 * @param cy
	 * @param cr
	 * @return
	 */
	public static boolean intRectCircle(
			double rx, double ry, double rw, double rh,
			double cx, double cy, double cr) {
		
		// Does the circle contain one of the rectangle's lines?
		if (intLineCircle(rx, ry, rx+rw, ry, cx, cy, cr))		return true; // TOP
		if (intLineCircle(rx, ry, rx, ry+rh, cx, cy, cr))		return true; // LEFT
		if (intLineCircle(rx, ry+rh, rx+rw, ry+rh, cx, cy, cr))	return true; // BOTTOM
		if (intLineCircle(rx+rw, ry, rx+rw, ry+rh, cx, cy, cr)) return true; // RIGHT
		
		// The circle is not intersecting the sides, so the only other possibility
		// is that the circle is completely inside the rectangle.
		if (cx < rx)	return false;
		if (cx > rx+rw)	return false;
		if (cy < ry)	return false;
		if (cy > ry+rh)	return false;
		
		return true;
	}
}
